package hu.mapro.model.analyzer;

import hu.mapro.model.generator.AutoBeanGenerator.ExtraInfo;
import hu.mapro.model.generator.ClassCustomizer;
import hu.mapro.model.generator.MetaFactory;
import hu.mapro.model.generator.MetaFactory.FieldInfo;
import hu.mapro.model.meta.DefinedType;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.OneToMany;

import org.aspectj.lang.annotation.Aspect;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;

public class ModelAnalyzer {
	
	private final Function<Class<?>, MetaFactory.TypeInfo<Class<?>>> function = new Function<Class<?>, MetaFactory.TypeInfo<Class<?>>>() {
		@Override
		public MetaFactory.TypeInfo<Class<?>> apply(
				final Class<?> clazz) {
			return new MetaFactory.TypeInfo<Class<?>>() {

				@Override
				public boolean isEnumeration() {
					return clazz.isEnum();
				}

				@Override
				public boolean isEntity() {
					return isAnnotationPresent(clazz, Entity.class);
				}

				@Override
				public String getFullName() {
					return clazz.getName();
				}

				@Override
				public Class<?> getSuperType() {
					if (clazz.getSuperclass().equals(Object.class)) return null;
					return clazz.getSuperclass();
				}

				@Override
				public Collection<FieldInfo<Class<?>>> getFields() {
					BeanInfo bi = introspectBean(clazz);
					
					return Collections2.transform(Arrays.asList(bi.getPropertyDescriptors()), new Function<PropertyDescriptor, FieldInfo<Class<?>>>() {

						@Override
						public FieldInfo<Class<?>> apply(
								final PropertyDescriptor pd) {
							return new FieldInfo<Class<?>>() {

								@Override
								public String getName() {
									return pd.getName();
								}

								@Override
								public String getReadMethod() {
									return pd.getReadMethod()==null?null:pd.getReadMethod().getName();
								}

								@Override
								public String getWriteMethod() {
									return pd.getWriteMethod()==null?null:pd.getWriteMethod().getName();
								}

								@Override
								public boolean isList() {
									return List.class.isAssignableFrom(pd.getPropertyType());
								}

								@Override
								public boolean isSet() {
									return Set.class.isAssignableFrom(pd.getPropertyType());
								}

								@Override
								public String getInverseFieldName() {
									OneToMany o2m = clazz.getAnnotation(OneToMany.class);
									if (o2m!=null) {
										return o2m.mappedBy();
									}
									return null;
								}

								@Override
								public Class<?> getValueType() {
									Class<?> propertyType = pd.getPropertyType();
									if (isList()||isSet()) {
										if (getReadMethod()!=null) {
											return (Class<?>) ((ParameterizedType)pd.getReadMethod().getGenericReturnType()).getActualTypeArguments()[0];
										} else {
											return (Class<?>) ((ParameterizedType)pd.getWriteMethod().getGenericParameterTypes()[0]).getActualTypeArguments()[0];
										}
									} else {
										return propertyType;
									}
								}
							};
						}
					});
				}

				@Override
				public ClassCustomizer getCustomizer() {
					return null;
				}

				@Override
				public boolean isAbstract() {
					return (clazz.getModifiers()&Modifier.ABSTRACT) != 0;
				}
			};
		}
	};
	
	Class<?>[] beanClasses;
	
	
	
	private MetaFactory<Class<?>> mf;
	
	public ModelAnalyzer(Class<?>... beanClasses) {
		this.beanClasses = beanClasses;
		mf = new MetaFactory<Class<?>>(ImmutableSet.copyOf(beanClasses), function);
	}

	
	public Collection<DefinedType<?>> getDefinedTypes() {
		return mf.generate();
	}
	
	public ExtraInfo getExtraInfo() {
		return mf.extraInfo;
	}
	
	private BeanInfo introspectBean(Class<?> beanClass) {
		try {
			if (beanClass.isInterface()) {
				return Introspector.getBeanInfo(beanClass);
			} else {
				return Introspector.getBeanInfo(beanClass, Object.class);
			}
			
		} catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}
	}
	

	public static boolean isAnnotationPresent(Class<?> clazz, Class<?> annotation) {
		for (Annotation a : clazz.getAnnotations()) {
			if (a.annotationType().getName().equals(annotation.getName())) {
				return true;
			}
		}
		return false;
	}

}
